From a5bc3c41b76a1c62db517b9fd5a547de3dad0428 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Mon, 3 Nov 2025 16:41:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=A3=8E=E9=99=A9?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E6=89=B9=E6=AC=A1=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/db_admin.html | 465 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 435 insertions(+), 30 deletions(-) diff --git a/static/db_admin.html b/static/db_admin.html index a668576..091fe47 100644 --- a/static/db_admin.html +++ b/static/db_admin.html @@ -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 = '
'; + html += ` +
+
+ 风险条目:${riskCount} 个 +
+
+ +
+
+ `; // 许可基本信息 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 += '
'; 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 ? `
${legalSegments.join('|')}
` : ''; - const statusHtml = item.permit_status ? `${escapeHtml(item.permit_status)}` : ''; - const changeSummary = item.change_summary ? `
备注:${escapeHtml(item.change_summary)}
` : ''; - 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 + ? `
备注:${escapeHtml(group.change_summaries.join(';'))}
` + : ''; + const statusTag = primary.permit_status ? `${escapeHtml(primary.permit_status)}` : ''; + const toggleButton = riskCount > 0 + ? `` + : ''; + const restoreButton = ``; + + 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 + ? `
${detailMetaParts.join('|')}
` + : ''; + const detailStatusTag = detail.permit_status ? `${escapeHtml(detail.permit_status)}` : ''; + const detailNote = detail.change_summary ? `
备注:${escapeHtml(detail.change_summary)}
` : ''; + return ` +
+
+ 版本 ${escapeHtml(String(detail.version || 0))} + 风险ID:${escapeHtml(detail.risk_id || '-')} + ${detailStatusTag} +
+
${escapeHtml(detail.risk_content || '—')}
+ ${detailMetaHtml} + ${detailNote} +
+ `; + }).join(''); + + const detailListHtml = (isExpanded && riskCount > 0) + ? ` +
+ ${detailItemsHtml} +
+ ` + : ''; + + const metaHtml = ` +
+ ${statusTag} + 风险条目:${riskCount} 个 + 编辑人:${editorsText} +
+ `; + const timeDisplay = escapeHtml(entry.timeText || ''); html += ` @@ -1709,18 +1981,19 @@
📝
-
风险快照 · 版本 ${escapeHtml(String(item.version || 0))}
+
风险快照 · ${riskCount > 1 ? `批次(${riskCount} 条)` : `版本 ${escapeHtml(String(primary.version || 0))}`}
${timeDisplay}
${permitName}(${regionName})
${riskPreview || '—'}
${legalHtml} -
- ${statusHtml} - 编辑人:${editorName} - 风险ID:${escapeHtml(item.risk_id || '-')} + ${metaHtml} + ${changeSummaryMerged} +
+ ${toggleButton} + ${restoreButton}
- ${changeSummary} + ${detailListHtml}
`; @@ -1777,14 +2050,16 @@ html += `
${warningMessages.join('
')}
`; } - 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 += `