feat: 优化风险快照批次展示
This commit is contained in:
parent
406fca7363
commit
a5bc3c41b7
|
|
@ -421,6 +421,48 @@
|
|||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.snapshot-detail-list {
|
||||
margin-top: 12px;
|
||||
display: none;
|
||||
border-left: 2px solid #e0e0e0;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.snapshot-detail-list.expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.snapshot-detail-item {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.snapshot-detail-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.snapshot-detail-header {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.snapshot-detail-meta {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.timeline-footer {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
|
|
@ -917,6 +959,33 @@
|
|||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.detail-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 8px;
|
||||
background: #eef2ff;
|
||||
border: 1px solid #d7dbff;
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
color: #3f51b5;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-meta strong {
|
||||
font-size: 16px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
|
|
@ -1121,8 +1190,10 @@
|
|||
let historyStack = []; // 历史记录栈
|
||||
let currentRegion = null;
|
||||
let currentTheme = null;
|
||||
let currentPermit = null;
|
||||
let pendingDangerOperation = null; // 待执行的危险操作
|
||||
let currentPermit = null;
|
||||
let currentPermitDetails = null;
|
||||
let pendingDangerOperation = null; // 待执行的危险操作
|
||||
let isDeletingPermit = false;
|
||||
|
||||
const permitRiskSnapshotState = {
|
||||
limit: 10,
|
||||
|
|
@ -1144,6 +1215,7 @@
|
|||
let checkpointListCache = [];
|
||||
let checkpointListLoading = false;
|
||||
let checkpointListError = '';
|
||||
let expandedSnapshotGroups = new Set();
|
||||
|
||||
// 步骤配置
|
||||
const steps = {
|
||||
|
|
@ -1268,6 +1340,7 @@
|
|||
currentRegion = { id: regionId, name: regionName };
|
||||
currentTheme = null;
|
||||
currentPermit = null;
|
||||
currentPermitDetails = null;
|
||||
|
||||
// 更新步骤
|
||||
goToStep(2);
|
||||
|
|
@ -1280,6 +1353,7 @@
|
|||
|
||||
currentTheme = { id: themeId, name: themeName };
|
||||
currentPermit = null;
|
||||
currentPermitDetails = null;
|
||||
|
||||
// 更新步骤
|
||||
goToStep(3);
|
||||
|
|
@ -1291,6 +1365,7 @@
|
|||
historyStack.push({ step: currentStep, permit: currentPermit });
|
||||
|
||||
currentPermit = { id: permitId, name: permitName, themeId: themeId };
|
||||
currentPermitDetails = null;
|
||||
|
||||
// 更新步骤
|
||||
goToStep(4);
|
||||
|
|
@ -1415,10 +1490,10 @@
|
|||
// 清理后续状态
|
||||
if (targetStep <= 2) {
|
||||
currentTheme = null;
|
||||
currentPermit = null;
|
||||
}
|
||||
if (targetStep <= 3) {
|
||||
currentPermit = null;
|
||||
currentPermitDetails = null;
|
||||
}
|
||||
|
||||
goToStep(targetStep);
|
||||
|
|
@ -1429,6 +1504,7 @@
|
|||
currentRegion = null;
|
||||
currentTheme = null;
|
||||
currentPermit = null;
|
||||
currentPermitDetails = null;
|
||||
historyStack = [];
|
||||
goToStep(1);
|
||||
}
|
||||
|
|
@ -1470,7 +1546,25 @@
|
|||
// 渲染许可详情
|
||||
function renderPermitDetails(permit) {
|
||||
const detailsArea = document.querySelector('.details-area');
|
||||
currentPermitDetails = permit;
|
||||
const riskCount = Array.isArray(permit.risks) ? permit.risks.length : 0;
|
||||
if (currentPermit) {
|
||||
currentPermit = { ...currentPermit, riskCount };
|
||||
}
|
||||
const deleteButtonLabel = isDeletingPermit ? '删除中...' : '删除许可';
|
||||
const deleteButtonDisabled = isDeletingPermit ? 'disabled' : '';
|
||||
|
||||
let html = '<div class="details-content">';
|
||||
html += `
|
||||
<div class="detail-toolbar">
|
||||
<div class="detail-meta">
|
||||
风险条目:<strong>${riskCount}</strong> 个
|
||||
</div>
|
||||
<div class="detail-actions">
|
||||
<button class="btn btn-danger" id="deletePermitBtn" ${deleteButtonDisabled} onclick="confirmDeleteCurrentPermit()">${deleteButtonLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 许可基本信息
|
||||
html += `
|
||||
|
|
@ -1525,6 +1619,118 @@
|
|||
detailsArea.innerHTML = html;
|
||||
}
|
||||
|
||||
function confirmDeleteCurrentPermit() {
|
||||
if (isDeletingPermit) {
|
||||
return;
|
||||
}
|
||||
if (!currentRegion || !currentTheme || !currentPermit) {
|
||||
alert('请先选择要删除的许可');
|
||||
return;
|
||||
}
|
||||
|
||||
const riskCount = currentPermit.riskCount !== undefined
|
||||
? currentPermit.riskCount
|
||||
: (currentPermitDetails && Array.isArray(currentPermitDetails.risks) ? currentPermitDetails.risks.length : 0);
|
||||
const confirmMessage = `确定要删除「${currentRegion.name} › ${currentTheme.name} › ${currentPermit.name}」吗?\n\n` +
|
||||
`此操作会删除 ${riskCount} 条风险关联(如存在)。系统会备份当前风险快照,但删除后需通过快照管理页面手动恢复。`;
|
||||
|
||||
if (!confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const summaryInput = prompt('请输入删除说明(可选,用于快照对比):', '');
|
||||
if (summaryInput === null) {
|
||||
return;
|
||||
}
|
||||
const changeSummary = summaryInput.trim();
|
||||
|
||||
deleteCurrentPermit(changeSummary);
|
||||
}
|
||||
|
||||
async function deleteCurrentPermit(changeSummary) {
|
||||
if (isDeletingPermit) {
|
||||
return;
|
||||
}
|
||||
if (!currentRegion || !currentTheme || !currentPermit) {
|
||||
alert('当前上下文缺失,无法删除');
|
||||
return;
|
||||
}
|
||||
|
||||
isDeletingPermit = true;
|
||||
toggleDeletePermitButton(true);
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
region_id: currentRegion.id,
|
||||
theme_id: currentTheme.id,
|
||||
permit_id: currentPermit.id
|
||||
};
|
||||
if (changeSummary) {
|
||||
payload.change_summary = changeSummary;
|
||||
}
|
||||
|
||||
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/permits', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
const snapshotCount = data.data && typeof data.data.snapshot_count === 'number'
|
||||
? data.data.snapshot_count
|
||||
: 0;
|
||||
const deletedRisks = data.data && data.data.deleted_rows
|
||||
? (data.data.deleted_rows.region_permit_risks || 0)
|
||||
: 0;
|
||||
const remainingPermits = data.data && typeof data.data.remaining_theme_permits === 'number'
|
||||
? data.data.remaining_theme_permits
|
||||
: null;
|
||||
const themeDetached = !!(data.data && data.data.theme_detached);
|
||||
const snapshotBatchId = data.data && data.data.snapshot_batch_id;
|
||||
|
||||
let successMessage = `✅ 删除成功!\n\n已备份 ${snapshotCount} 条风险快照,并删除 ${deletedRisks} 条风险关联。`;
|
||||
if (themeDetached) {
|
||||
successMessage += '\n对应主题已与该地区解除关联。';
|
||||
} else if (remainingPermits !== null) {
|
||||
successMessage += `\n该主题在此地区仍剩余 ${remainingPermits} 个许可。`;
|
||||
}
|
||||
if (snapshotBatchId) {
|
||||
successMessage += `\n快照批次:${snapshotBatchId}`;
|
||||
}
|
||||
alert(successMessage);
|
||||
|
||||
const modal = document.getElementById('checkpointModal');
|
||||
if (modal && modal.classList.contains('show')) {
|
||||
if ((!permitRiskSnapshotState.regionFilter || permitRiskSnapshotState.regionFilter === currentRegion.id) &&
|
||||
(!permitRiskSnapshotState.permitFilter || permitRiskSnapshotState.permitFilter === currentPermit.id)) {
|
||||
await refreshPermitRiskSnapshots(false);
|
||||
}
|
||||
}
|
||||
|
||||
currentPermitDetails = null;
|
||||
quickJump(3);
|
||||
} else {
|
||||
const message = data && data.message ? data.message : `删除失败(HTTP ${response.status})`;
|
||||
alert(`❌ 删除失败:${message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`❌ 删除失败:${error.message}`);
|
||||
} finally {
|
||||
isDeletingPermit = false;
|
||||
toggleDeletePermitButton(false);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDeletePermitButton(disabled) {
|
||||
const btn = document.getElementById('deletePermitBtn');
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
btn.disabled = disabled;
|
||||
btn.textContent = disabled ? '删除中...' : '删除许可';
|
||||
}
|
||||
|
||||
// 更新步骤指示器
|
||||
function updateStepIndicator(step) {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
|
|
@ -1564,6 +1770,7 @@
|
|||
checkpointListCache = [];
|
||||
checkpointListLoading = true;
|
||||
checkpointListError = '';
|
||||
expandedSnapshotGroups = new Set();
|
||||
|
||||
renderCheckpointManager(checkpointListCache);
|
||||
|
||||
|
|
@ -1685,23 +1892,88 @@
|
|||
html += '<div class="timeline-list">';
|
||||
|
||||
timelineItems.forEach(entry => {
|
||||
if (entry.type === 'snapshot') {
|
||||
const item = entry.raw;
|
||||
const permitName = escapeHtml(item.permit_name || '-');
|
||||
const regionName = escapeHtml(item.region_name || '-');
|
||||
const riskFull = item.risk_content || '';
|
||||
if (entry.type === 'snapshot-group') {
|
||||
const group = entry.raw || {};
|
||||
const groupKey = group.groupKey || group.snapshot_batch_id || '';
|
||||
const groupKeyJs = String(groupKey).replace(/'/g, "\\'");
|
||||
const riskItems = group.items || [];
|
||||
const riskCount = riskItems.length;
|
||||
const primary = riskItems[0] || {};
|
||||
const permitName = escapeHtml(group.permit_name || primary.permit_name || '-');
|
||||
const regionName = escapeHtml(group.region_name || primary.region_name || '-');
|
||||
const riskFull = primary.risk_content || '';
|
||||
const riskPreview = escapeHtml(truncateText(riskFull, 160));
|
||||
const legalSegments = [];
|
||||
if (item.legal_basis) {
|
||||
legalSegments.push(`📕 ${escapeHtml(item.legal_basis)}`);
|
||||
if (primary.legal_basis) {
|
||||
legalSegments.push(`📕 ${escapeHtml(primary.legal_basis)}`);
|
||||
}
|
||||
if (item.document_no) {
|
||||
legalSegments.push(`📄 ${escapeHtml(item.document_no)}`);
|
||||
if (primary.document_no) {
|
||||
legalSegments.push(`📄 ${escapeHtml(primary.document_no)}`);
|
||||
}
|
||||
const legalHtml = legalSegments.length ? `<div class="timeline-meta" style="gap:6px;">${legalSegments.join('<span>|</span>')}</div>` : '';
|
||||
const statusHtml = item.permit_status ? `<span class="snapshot-status-tag">${escapeHtml(item.permit_status)}</span>` : '';
|
||||
const changeSummary = item.change_summary ? `<div class="timeline-note">备注:${escapeHtml(item.change_summary)}</div>` : '';
|
||||
const editorName = escapeHtml(item.edited_by || '—');
|
||||
const editorsText = (group.editors && group.editors.length)
|
||||
? escapeHtml(group.editors.join('、'))
|
||||
: '—';
|
||||
const isExpanded = expandedSnapshotGroups.has(groupKey);
|
||||
const expandIcon = isExpanded ? '▲' : '▼';
|
||||
const toggleLabel = isExpanded ? '收起明细' : '展开明细';
|
||||
const changeSummaryMerged = group.change_summaries && group.change_summaries.length
|
||||
? `<div class="timeline-note">备注:${escapeHtml(group.change_summaries.join(';'))}</div>`
|
||||
: '';
|
||||
const statusTag = primary.permit_status ? `<span class="snapshot-status-tag">${escapeHtml(primary.permit_status)}</span>` : '';
|
||||
const toggleButton = riskCount > 0
|
||||
? `<button class="btn btn-warning btn-sm" onclick="toggleSnapshotGroup('${groupKeyJs}')">${expandIcon} ${toggleLabel}</button>`
|
||||
: '';
|
||||
const restoreButton = `<button class="btn btn-primary btn-sm" onclick="confirmRestoreSnapshotBatch('${groupKeyJs}')"><span>🛠️</span> 恢复</button>`;
|
||||
|
||||
const detailItemsHtml = riskItems.map(detail => {
|
||||
const detailLegal = [];
|
||||
if (detail.legal_basis) {
|
||||
detailLegal.push(`📕 ${escapeHtml(detail.legal_basis)}`);
|
||||
}
|
||||
if (detail.document_no) {
|
||||
detailLegal.push(`📄 ${escapeHtml(detail.document_no)}`);
|
||||
}
|
||||
const detailMetaParts = [];
|
||||
if (detail.edited_by) {
|
||||
detailMetaParts.push(`编辑人:${escapeHtml(detail.edited_by)}`);
|
||||
}
|
||||
detailMetaParts.push(...detailLegal);
|
||||
const detailMetaHtml = detailMetaParts.length
|
||||
? `<div class="snapshot-detail-meta">${detailMetaParts.join('<span>|</span>')}</div>`
|
||||
: '';
|
||||
const detailStatusTag = detail.permit_status ? `<span class="snapshot-status-tag">${escapeHtml(detail.permit_status)}</span>` : '';
|
||||
const detailNote = detail.change_summary ? `<div class="timeline-note" style="margin-top:6px;">备注:${escapeHtml(detail.change_summary)}</div>` : '';
|
||||
return `
|
||||
<div class="snapshot-detail-item">
|
||||
<div class="snapshot-detail-header">
|
||||
<span>版本 ${escapeHtml(String(detail.version || 0))}</span>
|
||||
<span>风险ID:${escapeHtml(detail.risk_id || '-')}</span>
|
||||
${detailStatusTag}
|
||||
</div>
|
||||
<div class="timeline-content">${escapeHtml(detail.risk_content || '—')}</div>
|
||||
${detailMetaHtml}
|
||||
${detailNote}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
const detailListHtml = (isExpanded && riskCount > 0)
|
||||
? `
|
||||
<div class="snapshot-detail-list expanded">
|
||||
${detailItemsHtml}
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
const metaHtml = `
|
||||
<div class="timeline-meta">
|
||||
${statusTag}
|
||||
<span>风险条目:${riskCount} 个</span>
|
||||
<span>编辑人:${editorsText}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const timeDisplay = escapeHtml(entry.timeText || '');
|
||||
|
||||
html += `
|
||||
|
|
@ -1709,18 +1981,19 @@
|
|||
<div class="timeline-icon">📝</div>
|
||||
<div class="timeline-body">
|
||||
<div class="timeline-header">
|
||||
<div class="timeline-title">风险快照 · 版本 ${escapeHtml(String(item.version || 0))}</div>
|
||||
<div class="timeline-title">风险快照 · ${riskCount > 1 ? `批次(${riskCount} 条)` : `版本 ${escapeHtml(String(primary.version || 0))}`}</div>
|
||||
<div class="timeline-time">${timeDisplay}</div>
|
||||
</div>
|
||||
<div class="timeline-subtitle">${permitName}<span style="margin-left: 6px; color: #666;">(${regionName})</span></div>
|
||||
<div class="timeline-content" title="${escapeHtml(riskFull)}">${riskPreview || '—'}</div>
|
||||
${legalHtml}
|
||||
<div class="timeline-meta">
|
||||
${statusHtml}
|
||||
<span>编辑人:${editorName}</span>
|
||||
<span>风险ID:${escapeHtml(item.risk_id || '-')}</span>
|
||||
${metaHtml}
|
||||
${changeSummaryMerged}
|
||||
<div class="timeline-actions">
|
||||
${toggleButton}
|
||||
${restoreButton}
|
||||
</div>
|
||||
${changeSummary}
|
||||
${detailListHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -1777,14 +2050,16 @@
|
|||
html += `<div class="error" style="margin-top: 10px;">${warningMessages.join('<br>')}</div>`;
|
||||
}
|
||||
|
||||
const snapshotSummaryText = totalSnapshots
|
||||
? `快照 ${snapshotStart}-${snapshotEnd} / ${totalSnapshots}`
|
||||
: '快照 0 / 0';
|
||||
const snapshotGroupCount = new Set((snapshotState.snapshots || []).map(item => item.snapshot_batch_id || item.snapshot_id)).size;
|
||||
const snapshotRangeText = totalSnapshots
|
||||
? `风险快照 ${snapshotStart}-${snapshotEnd} / ${totalSnapshots}`
|
||||
: '风险快照 0 / 0';
|
||||
const snapshotBatchText = `快照批次 ${snapshotGroupCount} 个`;
|
||||
const checkpointSummaryText = `检查点 ${checkpointList.length} 个`;
|
||||
|
||||
html += `
|
||||
<div class="timeline-footer">
|
||||
<div class="snapshot-count">${snapshotSummaryText},${checkpointSummaryText}</div>
|
||||
<div class="snapshot-count">${snapshotBatchText}|${snapshotRangeText},${checkpointSummaryText}</div>
|
||||
<div class="snapshot-pagination">
|
||||
<button class="btn btn-warning btn-sm" onclick="changeSnapshotPage(-1)" ${disablePrev}>上一页</button>
|
||||
<button class="btn btn-warning btn-sm" onclick="changeSnapshotPage(1)" ${disableNext}>下一页</button>
|
||||
|
|
@ -1965,6 +2240,7 @@
|
|||
permitRiskSnapshotState.error = error.message || '网络错误';
|
||||
} finally {
|
||||
permitRiskSnapshotState.loading = false;
|
||||
expandedSnapshotGroups = new Set();
|
||||
renderCheckpointManager(checkpointListCache);
|
||||
}
|
||||
}
|
||||
|
|
@ -2252,6 +2528,92 @@
|
|||
}
|
||||
}
|
||||
|
||||
function getSnapshotGroupItems(batchId) {
|
||||
if (!batchId) return [];
|
||||
return (permitRiskSnapshotState.snapshots || []).filter(item => {
|
||||
const key = item.snapshot_batch_id || item.snapshot_id;
|
||||
return key === batchId;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSnapshotGroup(batchId) {
|
||||
if (!batchId) return;
|
||||
if (expandedSnapshotGroups.has(batchId)) {
|
||||
expandedSnapshotGroups.delete(batchId);
|
||||
} else {
|
||||
expandedSnapshotGroups.add(batchId);
|
||||
}
|
||||
renderCheckpointManager(checkpointListCache);
|
||||
}
|
||||
|
||||
function confirmRestoreSnapshotBatch(batchId) {
|
||||
if (!batchId) {
|
||||
alert('未找到对应的快照批次');
|
||||
return;
|
||||
}
|
||||
const groupItems = getSnapshotGroupItems(batchId);
|
||||
if (groupItems.length === 0) {
|
||||
alert('未找到对应的快照记录');
|
||||
return;
|
||||
}
|
||||
const primary = groupItems[0];
|
||||
const regionName = primary.region_name || '未知地区';
|
||||
const permitName = primary.permit_name || '未知许可';
|
||||
const riskCount = groupItems.length;
|
||||
const confirmMessage = `确定要从快照恢复「${regionName} › ${permitName}」吗?\n\n` +
|
||||
`该操作将重新建立 ${riskCount} 条风险关联、许可明细以及相关主题/范围配置。`;
|
||||
if (!confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
const summaryInput = prompt('请输入恢复说明(可选):', '');
|
||||
if (summaryInput === null) {
|
||||
return;
|
||||
}
|
||||
const changeSummary = summaryInput.trim();
|
||||
restoreSnapshotBatch(batchId, changeSummary, groupItems);
|
||||
}
|
||||
|
||||
async function restoreSnapshotBatch(batchId, changeSummary, groupItems) {
|
||||
try {
|
||||
const payload = {};
|
||||
if (changeSummary) {
|
||||
payload.change_summary = changeSummary;
|
||||
}
|
||||
const response = await fetch(`/fs-ai-asistant/api/workflow/lawrisk/admin/permit-risk-snapshots/${batchId}/restore`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data && data.success) {
|
||||
const restoredCount = data.data && typeof data.data.restored_risk_count === 'number'
|
||||
? data.data.restored_risk_count
|
||||
: (groupItems ? groupItems.length : 0);
|
||||
alert(`✅ 恢复成功!已恢复 ${restoredCount} 条风险关联。`);
|
||||
await refreshPermitRiskSnapshots(false);
|
||||
|
||||
if (groupItems && groupItems.length > 0) {
|
||||
const targetRegionId = groupItems[0].region_id;
|
||||
const targetPermitId = groupItems[0].permit_id;
|
||||
if (currentRegion && currentRegion.id === targetRegionId) {
|
||||
if (currentTheme) {
|
||||
await loadPermits(currentTheme.id, currentTheme.name);
|
||||
}
|
||||
if (currentPermit && currentPermit.id === targetPermitId) {
|
||||
await showPermitDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const message = data && data.message ? data.message : `恢复失败(HTTP ${response.status})`;
|
||||
alert(`❌ 恢复失败:${message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`❌ 恢复失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkpointTimestampToIso(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const match = String(timestamp).match(/(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})/);
|
||||
|
|
@ -2261,15 +2623,58 @@
|
|||
|
||||
function buildTimelineItems(snapshotItems, checkpointItems) {
|
||||
const items = [];
|
||||
const snapshotGroups = new Map();
|
||||
|
||||
(snapshotItems || []).forEach(item => {
|
||||
const iso = item.created_at || '';
|
||||
const timeValue = Date.parse(iso) || 0;
|
||||
const groupKey = item.snapshot_batch_id || item.snapshot_id;
|
||||
if (!snapshotGroups.has(groupKey)) {
|
||||
snapshotGroups.set(groupKey, {
|
||||
key: groupKey,
|
||||
items: [],
|
||||
});
|
||||
}
|
||||
snapshotGroups.get(groupKey).items.push(item);
|
||||
});
|
||||
|
||||
snapshotGroups.forEach(group => {
|
||||
group.items.sort((a, b) => {
|
||||
const timeA = Date.parse(a.created_at || '') || 0;
|
||||
const timeB = Date.parse(b.created_at || '') || 0;
|
||||
return timeB - timeA;
|
||||
});
|
||||
|
||||
let latestIso = '';
|
||||
let latestValue = 0;
|
||||
group.items.forEach(item => {
|
||||
const value = Date.parse(item.created_at || '') || 0;
|
||||
if (value > latestValue) {
|
||||
latestValue = value;
|
||||
latestIso = item.created_at || '';
|
||||
}
|
||||
});
|
||||
|
||||
const firstItem = group.items[0] || {};
|
||||
const displayIso = latestIso || firstItem.created_at || '';
|
||||
const timeValue = displayIso ? Date.parse(displayIso) || 0 : latestValue;
|
||||
const editors = Array.from(new Set(group.items.map(entry => entry.edited_by).filter(Boolean)));
|
||||
const summaries = group.items.map(entry => entry.change_summary).filter(Boolean);
|
||||
|
||||
items.push({
|
||||
type: 'snapshot',
|
||||
type: 'snapshot-group',
|
||||
timeValue,
|
||||
timeText: iso ? formatIsoDatetime(iso) : '',
|
||||
raw: item
|
||||
timeText: displayIso ? formatIsoDatetime(displayIso) : '',
|
||||
raw: {
|
||||
groupKey: group.key,
|
||||
created_at: displayIso,
|
||||
items: group.items,
|
||||
region_name: firstItem.region_name || '',
|
||||
region_id: firstItem.region_id || '',
|
||||
permit_name: firstItem.permit_name || '',
|
||||
permit_id: firstItem.permit_id || '',
|
||||
editors,
|
||||
change_summaries: summaries,
|
||||
snapshot_batch_id: firstItem.snapshot_batch_id || firstItem.snapshot_id || group.key,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue