feat(client): track digital sync retry history
This commit is contained in:
@@ -1698,6 +1698,59 @@
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.de-sync-attempt-history {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(126,163,194,0.2);
|
||||
border-radius: 14px;
|
||||
background: rgba(247,251,255,0.82);
|
||||
}
|
||||
.de-sync-attempt-history > span {
|
||||
color: #6f8196;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.de-sync-attempt-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.de-sync-attempt-row {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
padding: 9px 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255,255,255,0.72);
|
||||
}
|
||||
.de-sync-attempt-row > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.de-sync-attempt-row strong {
|
||||
color: #153754;
|
||||
font-size: 12px;
|
||||
}
|
||||
.de-sync-attempt-row em {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
.de-sync-attempt-row span,
|
||||
.de-sync-attempt-row small {
|
||||
color: #667b90;
|
||||
font-size: 12px;
|
||||
}
|
||||
.de-sync-attempt-row small {
|
||||
line-height: 1.45;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.de-sync-attempt-row--success em { color: #23805d; }
|
||||
.de-sync-attempt-row--danger em { color: #bb3b3b; }
|
||||
.de-sync-attempt-row--info em { color: #2e6fa5; }
|
||||
.de-event-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -64,6 +64,15 @@ interface QimingclawSyncStatus {
|
||||
recentFailures?: QimingclawSyncFailure[];
|
||||
}
|
||||
|
||||
interface QimingclawSyncAttempt {
|
||||
attemptNo: number;
|
||||
status: string;
|
||||
error: string | null;
|
||||
endpoint?: string | null;
|
||||
startedAt: string;
|
||||
finishedAt: string;
|
||||
}
|
||||
|
||||
interface QimingclawSyncFailure {
|
||||
id: string;
|
||||
entityType: string;
|
||||
@@ -74,6 +83,7 @@ interface QimingclawSyncFailure {
|
||||
nextRetryAt?: string | null;
|
||||
dueForRetry?: boolean;
|
||||
managementRecordUrl?: string | null;
|
||||
attemptHistory?: QimingclawSyncAttempt[];
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
@@ -64,6 +64,15 @@ interface ManagementSyncStatus {
|
||||
recentFailures?: ManagementSyncFailure[];
|
||||
}
|
||||
|
||||
interface ManagementSyncAttempt {
|
||||
attemptNo: number;
|
||||
status: string;
|
||||
error: string | null;
|
||||
endpoint?: string | null;
|
||||
startedAt: string;
|
||||
finishedAt: string;
|
||||
}
|
||||
|
||||
interface ManagementSyncFailure {
|
||||
id: string;
|
||||
entityType: string;
|
||||
@@ -74,6 +83,7 @@ interface ManagementSyncFailure {
|
||||
nextRetryAt?: string | null;
|
||||
dueForRetry?: boolean;
|
||||
managementRecordUrl?: string | null;
|
||||
attemptHistory?: ManagementSyncAttempt[];
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -495,6 +505,18 @@ function syncFailureRetryTime(failure: ManagementSyncFailure): string {
|
||||
return formatBeijingDateTime(failure.nextRetryAt) || '待计算';
|
||||
}
|
||||
|
||||
function syncAttemptStatusLabel(status: string): string {
|
||||
if (status === 'synced') return '已同步';
|
||||
if (status === 'failed') return '失败';
|
||||
return status || '未知';
|
||||
}
|
||||
|
||||
function syncAttemptTone(status: string): 'success' | 'danger' | 'info' {
|
||||
if (status === 'synced') return 'success';
|
||||
if (status === 'failed') return 'danger';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
async function readManagementSyncStatus(): Promise<ManagementSyncStatus | null> {
|
||||
const bridge = window.QimingClawBridge?.digital;
|
||||
if (!bridge?.getSyncStatus) return null;
|
||||
@@ -1837,6 +1859,31 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
<span>失败原因</span>
|
||||
<p>{selectedSyncFailure.error || '暂无错误详情。'}</p>
|
||||
</div>
|
||||
{(selectedSyncFailure.attemptHistory?.length ?? 0) > 0 && (
|
||||
<div className="de-sync-attempt-history">
|
||||
<span>重试历史</span>
|
||||
<div className="de-sync-attempt-list">
|
||||
{selectedSyncFailure.attemptHistory?.map((attempt) => (
|
||||
<div
|
||||
key={`${attempt.attemptNo}-${attempt.finishedAt}`}
|
||||
className={`de-sync-attempt-row de-sync-attempt-row--${syncAttemptTone(
|
||||
attempt.status,
|
||||
)}`}
|
||||
>
|
||||
<div>
|
||||
<strong>第 {attempt.attemptNo} 次</strong>
|
||||
<em>{syncAttemptStatusLabel(attempt.status)}</em>
|
||||
<span>
|
||||
{formatBeijingDateTime(attempt.finishedAt) ||
|
||||
attempt.finishedAt}
|
||||
</span>
|
||||
</div>
|
||||
{attempt.error && <small>{attempt.error}</small>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="de-event-modal-actions">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user